/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package Aplicación;

/**
 *
 * @author MariaDeFreitas
 */
import java.io.*;
import java.net.*;
import java.text.SimpleDateFormat;
import java.util.*;

/*
 * El Servidor
 */
public class Server {
	// Un ID unico para cada conexion
	private static int uniqueId;
	// Lista de Clientes
	public static ArrayList<ClientThread> al;
	// if I am in a GUI
	private ServerGUI sg;
	// Para mostrar el tiempo
	private SimpleDateFormat sdf;
	//Número de puerto para escuchar la conexion
	private int port;
	// the boolean that will be turned of to stop the server
	private boolean keepGoing;

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    public static int getUniqueId() {
        return uniqueId;
    }

    public static void setUniqueId(int uniqueId) {
        Server.uniqueId = uniqueId;
    }

    public static ArrayList<ClientThread> getAl() {
        return al;
    }

    public void setAl(ArrayList<ClientThread> al) {
        this.al = al;
    }

    public Server() {
    }


	/*
	 *  server constructor that receive the port to listen to for connection as parameter
	 *  in console
	 */
	public Server(int port) {
		this(port, null);
	}
	
	public Server(int port, ServerGUI sg) {
		// GUI or not
		this.sg = sg;
		// the port
		this.port = port;
		// to display hh:mm:ss
		sdf = new SimpleDateFormat("HH:mm:ss");
		// ArrayList for the Client list
		al = new ArrayList<ClientThread>();
	}

	public void start() {
		keepGoing = true;
		/* create socket server and wait for connection requests */
		try 
		{
			// the socket used by the server
			ServerSocket serverSocket = new ServerSocket(port);
			// infinite loop to wait for connections
			while(keepGoing) 
			{
				// format message saying we are waiting
				display("Servidor esperando por clientes en el puerto " + port + ".");
				
				Socket socket = serverSocket.accept();  	// accept connection
				// if I was asked to stop
				if(!keepGoing) {
                                break;
                            }
				ClientThread t = new ClientThread(socket);  // make a thread of it
				al.add(t);		                   // save it in the ArrayList
				t.start();
                            
			}
			// I was asked to stop
			try {
				serverSocket.close();
				for(int i = 0; i < al.size(); ++i) {
					ClientThread tc = al.get(i);
					try {
					tc.sInput.close();
					tc.sOutput.close();
					tc.socket.close();
					}
					catch(IOException ioE) {
						// not much I can do
					}
				}
			}
			catch(Exception e) {
				display("Exception closing the server and clients: " + e);
			}
		}
		// something went bad
		catch (IOException e) {
            String msg = sdf.format(new Date()) + " Exception on new ServerSocket: " + e + "\n";
			//display(msg);
		}
	}		
    /*
     * For the GUI to stop the server
     */
	protected void stop() {
		keepGoing = false;
		// connect to myself as Client to exit statement 
		// Socket socket = serverSocket.accept();
		try {
                     new Socket("localhost", port);
		}
		catch(Exception e) {
			// nothing I can really do
		}
	}
	/*
	 * Display an event (not a message) to the console or the GUI
	 */
	private void display(String msg) {
		String time = sdf.format(new Date()) + " " + msg;
		if(sg == null) {
                System.out.println(time);
            }
		else {
                sg.appendEvent(time + "\n");
            }
	}
	/*
	 *  to broadcast a message to all Clients
	 */
	private synchronized void broadcast(String message) {
		// add HH:mm:ss and \n to the message
		String time = sdf.format(new Date());
		String messageLf = time + " " + message + "\n";
		// display message on console or GUI
		if(sg == null) {
                System.out.print(messageLf);
            }
		else {
                sg.appendRoom(messageLf);
            }     // append in the room window
		
		// we loop in reverse order in case we would have to remove a Client
		// because it has disconnected
		for(int i = al.size(); --i >= 0;) {
			ClientThread ct = al.get(i);
			// try to write to the Client if it fails remove it from the list
			if(!ct.writeMsg(messageLf)) {
				al.remove(i);
				display("Desconectado Cliente " + ct.username + " Eliminado de la lista.");
			}
		}
	}

	// for a client who logoff using the LOGOUT message
	synchronized void remove(int id) {
		// scan the array list until we found the Id
		for(int i = 0; i < al.size(); ++i) {
			ClientThread ct = al.get(i);
			// found it
			if(ct.id == id) {
				al.remove(i);
				return;
			}
		}
	}
	
	

	/** One instance of this thread will run for each client */
	class ClientThread extends Thread {
		// the socket where to listen/talk
		Socket socket;
		ObjectInputStream sInput;
		ObjectOutputStream sOutput;
		// my unique id (easier for deconnection)
		int id;
		// the Username of the Client
		String username;
		// the only type of message a will receive
		Mensaje cm;
		// the date I connect
		String date;
                //ip de cada cliente
                InetAddress ip;
                
        public int getId1() {
            return id;
        }

        public void setId(int id) {
            this.id = id;
        }

        public String getUsername() {
            return username;
        }

        public void setUsername(String username) {
            this.username = username;
        }

		// Constructore
		ClientThread(Socket socket) {
			// a unique id
			id = ++uniqueId;
			this.socket = socket;
                        this.ip=socket.getInetAddress();
			/* Creating both Data Stream */
			System.out.println("Hilo tratando de crear Object Input/Output Streams");
			try
			{
				// create output first
				sOutput = new ObjectOutputStream(socket.getOutputStream());
				sInput  = new ObjectInputStream(socket.getInputStream());
				// read the username
				username = (String) sInput.readObject();
				display(username + " Acaba de conectarse.");
			}
			catch (IOException e) {
				display("Exception creando nuevos Input/output Streams: " + e);
				return;
			}
			// have to catch ClassNotFoundException
			// but I read a String, I am sure it will work
			catch (ClassNotFoundException e) {
			}
            date = new Date().toString() + "\n";
		}

		// what will run forever
            @Override
		public void run() {
			// to loop until LOGOUT
			boolean keepGoing = true;
			while(keepGoing) {
				// read a String (which is an object)
				try {
					cm = (Mensaje) sInput.readObject();
				}
				catch (IOException e) {
					display(username + " Excepción leyendo Streams: " + e);
					break;				
				}
				catch(ClassNotFoundException e2) {
					break;
				}
				// the messaage part of the ChatMessage
				String message = cm.getMessage();

				// Switch on the type of message receive
				switch(cm.getType()) {

				case Mensaje.MESSAGE:
					broadcast(username + ": " + message);
					break;
				case Mensaje.LOGOUT:
					display(username + " desconectado.");
					keepGoing = false;
					break;
				case Mensaje.WHOISIN:
					writeMsg("Lista de usuarios conectados a las " + sdf.format(new Date()) + "\n");
					// scan al the users connected
					for(int i = 0; i < al.size(); ++i) {
						ClientThread ct = al.get(i);
						writeMsg((i+1) + ") " + ct.username + " desde " + ct.date);
					}
					break;
				}
			}
			// remove myself from the arrayList containing the list of the
			// connected Clients
			remove(id);
			close();
		}
		
		// try to close everything
            @SuppressWarnings("empty-statement")
		private void close() {
			// try to close the connection
			try {
				if(sOutput != null) {
                                sOutput.close();
                            }
			}
			catch(Exception e) {}
			try {
				if(sInput != null) {
                                sInput.close();
                            }
			}
			catch(Exception e) {};
			try {
				if(socket != null) {
                                socket.close();
                            }
			}
			catch (Exception e) {}
		}

		/*
		 * Write a String to the Client output stream
		 */
		private boolean writeMsg(String msg) {
			// if Client is still connected send the message to it
			if(!socket.isConnected()) {
				close();
				return false;
			}
			// write the message to the stream
			try {
				sOutput.writeObject(msg);
			}
			// if an error occurs, do not abort just inform the user
			catch(IOException e) {
				display("Error enviando mensaje a " + username);
				display(e.toString());
			}
			return true;
		}
	}
}

